import { Entity } from "./entity";
import { EnvironmentContext } from "./environment";

export interface CyberspaceConfig {
    /** 实体对象 */
    entities?: Record<string, Entity>;
    /** 上下文 */
    context?: EnvironmentContext;
}

export class Cyberspace {
    /** 实体对象 */
    public entities: Record<string, Entity>;
    /** 上下文 */
    public context: EnvironmentContext;

    constructor(config?: CyberspaceConfig) {
        this.entities = config?.entities ?? {};
        this.context = config?.context ?? new EnvironmentContext({ frame: 0 });
    }

    reset(config?: CyberspaceConfig) {
        this.entities = config?.entities ?? this.entities ?? [];
        this.context = config?.context ?? new EnvironmentContext({ frame: 0 });
        Object.values(this.entities).forEach((entity) => {
            entity.context.reset();
        });
    }

    addEntity(entity: Entity) {
        entity.context.frame = this.context.frame;
        if (this.entities[entity.name]) {
            throw new Error(`Entity ${entity.name} already exists`);
        }
        this.entities[entity.name] = entity;
    }

    async nextFrame(): Promise<void> {
        if (this.context.exited) {
            return;
        }

        this.context.update({ frame: this.context.frame + 1 });
        for (const entity of Object.values(this.entities)) {
            await this.updateEntity(entity);
        }
    }

    async nextFrameAsync(): Promise<void> {
        if (this.context.exited) {
            return;
        }

        this.context.update({ frame: this.context.frame + 1 });
        const promises = Object.values(this.entities).map(async (entity) => {
            await this.updateEntityTemp(entity);
        });
        await Promise.all(promises);
        Object.values(this.entities).forEach((entity) => {
            entity.context.updateTempToReal();
        });
    }

    async runLoop(steps: number, interval?: number): Promise<void> {
        while (!this.context.exited && steps-- > 0) {
            await this.nextFrame();
            await this.waitInterval(interval ?? 0);
        }
    }

    async runLoopAsync(steps: number, interval?: number): Promise<void> {
        while (!this.context.exited && steps-- > 0) {
            await this.nextFrameAsync();
            await this.waitInterval(interval ?? 0);
        }
    }

    private async waitInterval(interval: number): Promise<void> {
        if (interval < 1) return;
        await new Promise((resolve) =>
            setTimeout(() => {
                resolve(null);
            }, interval)
        );
    }

    private async updateEntity(entity: Entity): Promise<void> {
        const result = await entity.action(this.entities, this.context);
        entity.context.update({ result, frame: this.context.frame });
    }

    private async updateEntityTemp(entity: Entity): Promise<void> {
        const result = await entity.action(this.entities, this.context);
        entity.context.updateTemp({ result, frame: this.context.frame });
    }
}
